<?php
namespace app\service;

use Simps\MQTT\Client;
use Simps\MQTT\Config\ClientConfig;

class MqttClient
{
    protected $client;
    protected $isConnected = false;
    protected $config = [
        'host'          => 'broker.emqx.io',
        'port'          => 1883,
        'username'      => '',
        'password'      => '',
        'client_id'     => '' ,
        'keepalive'     => 60,
        'timeout'       => 5,      // 连接超时时间（秒）
        'clean_session' => true,   // 清除会话标志
        'max_attempts'  => 3,      // 最大重连次数
        'buffer_size'   => 1024 * 1024, // 缓冲区大小(字节)
        'debug'         => false,  // 调试模式
    ];
    
    protected $lastPingTime = 0;
    protected $reconnectAttempts = 0;
    protected $lastErrorMessage = '';

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
        if (empty($this->config['client_id'])) {
            $this->config['client_id'] = 'tp5_client_' . uniqid();
        }
 
        $this->config['port'] = (int)$this->config['port'];
        $this->config['keepalive'] = (int)$this->config['keepalive'];
        $this->config['timeout'] = (int)($this->config['timeout'] ?? 5);
        
        // 确保 client_id 是字符串
        $this->config['client_id'] = (string)$this->config['client_id'];
        
        $this->initClient();
    }
    
    /**
     * 初始化MQTT客户端
     */
    protected function initClient()
    {
        try {
            $clientConfig = new ClientConfig();
            $clientConfig->setUserName($this->config['username'])
                        ->setPassword($this->config['password'])
                        ->setKeepAlive($this->config['keepalive'])
                        ->setClientId($this->config['client_id'])
                        ->setSwooleConfig([
                            'open_tcp_nodelay' => true,    // 启用TCP_NODELAY
                            'connect_timeout' => $this->config['timeout'], // 连接超时
                            'socket_buffer_size' => $this->config['buffer_size'], // 套接字缓冲区大小
                            'read_timeout' => $this->config['timeout'], // 读取超时
                            'write_timeout' => $this->config['timeout'], // 写入超时
                        ]);
            
            // 使用非协程客户端模式 (SYNC_CLIENT_TYPE = 2)
            $this->client = new Client(
                $this->config['host'],    // 主机地址
                $this->config['port'],    // 端口号 (整数)
                $clientConfig,            // 配置对象
                2                         // 使用同步客户端类型
            );
            
            return true;
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            $this->isConnected = false;
            return false;
        }
    }

    /**
     * 连接到 MQTT 服务器
     * @return bool 连接是否成功
     */
    public function connect(): bool
    {
        $this->reconnectAttempts = 0;
        
        try {
            // 如果客户端未初始化，先初始化
            if (!$this->client) {
                if (!$this->initClient()) {
                    return false;
                }
            }
            
            $result = $this->client->connect($this->config['clean_session']);

            // 当返回true或数组时表示连接成功
            $this->isConnected = ($result === true || is_array($result));
            
            if ($this->isConnected) {
                $this->lastPingTime = time();
                $this->lastErrorMessage = '';
            } else {
                $this->lastErrorMessage = 'Connect failed: Unknown error';
            }
            
            return $this->isConnected;
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            $this->isConnected = false;
            return false;
        }
    }

    /**
     * 发布消息
     */
    public function publish(string $topic, string $message, int $qos = 0): bool
    {
        if (!$this->ensureConnected()) {
            return false;
        }
        
        try {
            $result = $this->client->publish($topic, $message, $qos);
            if ($result) {
                $this->lastPingTime = time();
                return true;
            }
            return false;
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            $this->isConnected = false;
            return $this->reconnectAndRetry('publish', [$topic, $message, $qos]);
        }
    }

    /**
     * 订阅主题
     * @return array 订阅结果
     */
    public function subscribe(array $topics)
    {
        if (!$this->ensureConnected()) {
            return false;
        }
        
        try {
            $result = $this->client->subscribe($topics);
            if ($result) {
                $this->lastPingTime = time();
            }
            return $result;
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            $this->isConnected = false;
            return $this->reconnectAndRetry('subscribe', [$topics]);
        }
    }

    /**
     * 接收消息
     */
    public function receive()
    {
        if (!$this->ensureConnected()) {
            return false;
        }
        
        try {
            $result = $this->client->recv();
            
            // 更新最后活动时间
            if ($result !== false) {
                $this->lastPingTime = time();
            }
            
            return $result;
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            
            // 如果是连接已关闭的错误，尝试重连
            if (strpos($e->getMessage(), 'not connected') !== false) {
                $this->isConnected = false;
                return false;
            }
            
            // 其他错误直接返回false
            return false;
        }
    }
    
    /**
     * 确保客户端已连接
     */
    protected function ensureConnected(): bool
    {
        if (!$this->isConnected) {
            return $this->reconnect();
        }
        
        // 检查是否需要发送心跳包
        return $this->checkConnection();
    }
    
    /**
     * 检查连接状态并发送心跳包
     */
    protected function checkConnection(): bool
    {
        // 检查是否需要发送心跳包
        $now = time();
        $interval = $this->config['keepalive'] * 0.8; // 在keepalive的80%时间点发送心跳
        
        if ($now - $this->lastPingTime >= $interval) {
            try {
                $this->client->ping();
                $this->lastPingTime = $now;
                return true;
            } catch (\Throwable $e) {
                $this->lastErrorMessage = $e->getMessage();
                $this->isConnected = false;
                return $this->reconnect();
            }
        }
        
        return true;
    }
    
    /**
     * 重连并重试上一个操作
     */
    protected function reconnectAndRetry(string $method, array $params)
    {
        if ($this->reconnect()) {
            // 重连成功，重试操作
            try {
                return call_user_func_array([$this->client, $method], $params);
            } catch (\Throwable $e) {
                $this->lastErrorMessage = $e->getMessage();
                return false;
            }
        }
        return false;
    }
    
    /**
     * 尝试重新连接
     */
    public function reconnect(): bool
    {
        if ($this->reconnectAttempts >= $this->config['max_attempts']) {
            $this->lastErrorMessage = 'Max reconnect attempts reached';
            return false;
        }
        
        $this->reconnectAttempts++;
        
        // 重新初始化客户端
        if (!$this->initClient()) {
            return false;
        }
        
        try {
            $result = $this->client->connect($this->config['clean_session']);
            $this->isConnected = ($result === true || is_array($result));

            if ($this->isConnected) {
                $this->lastPingTime = time();
                $this->reconnectAttempts = 0;
                $this->lastErrorMessage = '';
                return true;
            } else {
                $this->lastErrorMessage = 'Reconnect failed';
                return false;
            }
        } catch (\Throwable $e) {
            $this->lastErrorMessage = $e->getMessage();
            $this->isConnected = false;
            return false;
        }
    }

    /**
     * 断开连接
     */
    public function disconnect(): void
    {
        if ($this->isConnected && $this->client) {
            try {
                $this->client->disconnect();
            } catch (\Throwable $e) {
                // 忽略断开连接时的错误
            }
            $this->isConnected = false;
        }
    }
    
    /**
     * 检查是否已连接
     */
    public function isConnected(): bool
    {
        return $this->isConnected;
    }
    
    /**
     * 获取最后一次错误信息
     */
    public function getLastError(): string
    {
        return $this->lastErrorMessage;
    }
    
    /**
     * 析构函数，确保连接被正确关闭
     */
    public function __destruct()
    {
        $this->disconnect();
    }
}